﻿using System;
using System.Net;
using System.Net.Sockets;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Security;
using NotificationTester.Microsoft.Exchange.WebServices;

namespace NotificationTester
{
	/// <summary>
	/// Default implementation for the <see cref="IExchangeWebServicesAdapter"/> interface. Uses an Exchange 2007 SP1 WebService proxy and
	/// WCF to manage subscriptions.
	/// </summary>
	public sealed class NotificationServiceConnector : IDisposable
	{
		private ChannelFactory<INotificationServicePortType> _ChannelFactory;


		/// <summary>
		/// Initializes a new instance of the <see cref="NotificationServiceConnector"/> class.
		/// </summary>
		/// <param name="serviceUrl">An url pointing to the CAS role of an Exchange 2007 or later server.</param>
		public NotificationServiceConnector(Uri serviceUrl)
		{
			if (serviceUrl == null) throw new ArgumentNullException("serviceUrl");
			ServiceUrl = serviceUrl;
			CreateWcfChannelFactory();
		}

		/// <summary>
		/// Gets or sets the url pointing to the notification service
		/// </summary>
		/// <value>The exchange server URL.</value>
		private Uri ServiceUrl { get; set; }

		public TestResult TestConnection()
		{
			try
			{
				var hostEntry = Dns.GetHostEntry(ServiceUrl.Host);
				if (hostEntry.AddressList.Length == 0)
				{
					return TestResult.DnsResolutionFailed;
				}
				var request = new SendNotificationRequest
								{
									SendNotification = new SendNotificationResponseType
														{
															ResponseMessages = new ArrayOfResponseMessagesType
																				{
																					ItemsElementName = new[]
				              		                   		                   		                   	{
				              		                   		                   		                   		ItemsChoiceType3.
				              		                   		                   		                   			SendNotificationResponseMessage
				              		                   		                   		                   	},
																					Items = new ResponseMessageType[]
				              		                   		                   		        	{
				              		                   		                   		        		new SendNotificationResponseMessageType
				              		                   		                   		        			{
				              		                   		                   		        				ResponseClass = ResponseClassType.Success,
				              		                   		                   		        				ResponseCode = ResponseCodeType.NoError,
				              		                   		                   		        				ResponseCodeSpecified = true,
				              		                   		                   		        				Notification = new NotificationType
				              		                   		                   		        				               	{
				              		                   		                   		        				               		MoreEvents = false,
				              		                   		                   		        				               		SubscriptionId =
				              		                   		                   		        				               			"FQB3MmszeDY0LmNvbnRvc28ubG9jYWwQAAAAvG9b8vMJeE2ziKIOgoX3Mw==",
				              		                   		                   		        				               		PreviousWatermark =
				              		                   		                   		        				               			"AQAAAPzMf/wfHmxKgYYWSA19UMh1FwAAAAAAAAE=",
				              		                   		                   		        				               		Items = new[]
				              		                   		                   		        				               		        	{
				              		                   		                   		        				               		        		new BaseNotificationEventType
				              		                   		                   		        				               		        			{
				              		                   		                   		        				               		        				Watermark =
				              		                   		                   		        				               		        					"AQAAAPzMf/wfHmxKgYYWSA19UMh1FwAAAAAAAAE="
				              		                   		                   		        				               		        			}
				              		                   		                   		        				               		        	},
				              		                   		                   		        				               		ItemsElementName =
				              		                   		                   		        				               			new[]
				              		                   		                   		        				               				{
				              		                   		                   		        				               					ItemsChoiceType.
				              		                   		                   		        				               						StatusEvent
				              		                   		                   		        				               				}
				              		                   		                   		        				               	}
				              		                   		                   		        			}
				              		                   		                   		        	}
																				}
														}
								};
				ExecuteExchangeMethod(service => service.SendNotification(request));
			}
			catch (EndpointNotFoundException)
			{
				return TestResult.ConnectFailed;
			}
			catch (SecurityNegotiationException)
			{
				return TestResult.ServerCertificateUntrusted;
			}
			catch (TimeoutException)
			{
				return TestResult.Timeout;
			}
			catch (SocketException ex)
			{
				if (ex.SocketErrorCode == SocketError.HostNotFound) return TestResult.DnsResolutionFailed;
				throw;
			}
			return TestResult.Success;
		}

		/// <summary>
		/// Creates the WCF channel factory which is used to call into the Exchange 2007 server.
		/// </summary>
		private void CreateWcfChannelFactory()
		{
			Binding binding = new BasicHttpBinding(ServiceUrl.Scheme == Uri.UriSchemeHttps
			                                       	? BasicHttpSecurityMode.Transport
			                                       	: BasicHttpSecurityMode.None);

			_ChannelFactory = new ChannelFactory<INotificationServicePortType>(binding, ServiceUrl.ToString());
		}

		/// <summary>
		/// Executes the exchange method and performs some clean-up operations in case of a failure.
		/// </summary>
		/// <typeparam name="TReturn">The type of the return.</typeparam>
		/// <param name="code">The code to execute.</param>
		/// <returns>The value returned from the WebService call.</returns>
		private TReturn ExecuteExchangeMethod<TReturn>(Func<INotificationServicePortType, TReturn> code)
		{
			IChannel channel = (IChannel) _ChannelFactory.CreateChannel();

			bool error = true;
			try
			{
				TReturn result = code((INotificationServicePortType)channel);
				channel.Close();
				error = false;
				return result;
			}
			finally
			{
				if (error)
				{
					channel.Abort();
				}
			}
		}

		public void Dispose()
		{
			if (_ChannelFactory != null)
			{
				_ChannelFactory.Close();
			}
		}
	}
}